package com.ifacebox.face.track.ui;

import com.arcsoft.face.FaceFeature;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.Rect;
import com.arcsoft.face.toolkit.ImageFactory;
import com.arcsoft.face.toolkit.ImageInfo;
import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamPanel;
import com.github.sarxos.webcam.WebcamResolution;
import com.ifacebox.face.track.model.FaceTrack;
import com.ifacebox.face.track.sevice.FaceEnginePooledService;
import com.ifacebox.face.track.sevice.FaceEngineService;
import com.ifacebox.face.track.sevice.FaceThreadService;
import com.ifacebox.face.track.utils.DebugUtils;
import com.ifacebox.face.track.utils.ImageUtils;

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author znn
 */
public class FaceWebCamPanel extends JPanel implements Runnable, WebcamPanel.Painter {
    private Webcam webCam;
    private WebcamPanel webCamPanel;
    private WebcamPanel.Painter webCamPainter;
    private final FaceThreadService faceThreadService;
    private final FaceEnginePooledService.FaceEnginePool faceEnginePool;
    private final List<FaceInfo> faceInfoList = new CopyOnWriteArrayList<>();
    private final AtomicBoolean running = new AtomicBoolean(false);
    private final Callback callback;

    public FaceWebCamPanel(FaceThreadService faceThreadService, FaceEnginePooledService.FaceEnginePool faceEnginePool, Callback callback) {
        this.faceThreadService = faceThreadService;
        this.faceEnginePool = faceEnginePool;
        this.callback = callback;
        super.setLayout(new BorderLayout());
        super.setBackground(Color.BLACK);
        super.setPreferredSize(WebcamResolution.VGA.getSize());
    }


    public void stop() {
        if (this.webCamPanel != null) {
            this.webCamPanel.stop();
            this.webCamPanel.setComponentPopupMenu(null);
            super.remove(this.webCamPanel);
            this.webCam.close();
            this.webCamPanel = null;
        }
        if (this.running.compareAndSet(true, false)) {
            this.faceThreadService.remove(this);
        }
    }

    public void start(Webcam webCam) {
        this.webCam = webCam;
        this.webCam.setViewSize(WebcamResolution.VGA.getSize());
        this.webCamPanel = new WebcamPanel(this.webCam, false);
        this.webCamPanel.setPainter(this);
        this.webCamPanel.setMirrored(false);
        this.webCamPanel.setFPSDisplayed(true);
        this.webCamPanel.setImageSizeDisplayed(true);
        this.webCamPanel.setDrawMode(WebcamPanel.DrawMode.FIT);
        this.webCamPanel.setComponentPopupMenu(new FaceWebCamPopupMenu(this.webCam, this.webCamPanel));
        this.webCamPainter = webCamPanel.getDefaultPainter();
        super.add(this.webCamPanel, BorderLayout.CENTER);
        this.webCamPanel.start();
        if (this.running.compareAndSet(false, true)) {
            this.faceThreadService.execute(this);
        }
    }

    @Override
    public void paintPanel(WebcamPanel panel, Graphics2D g2) {
        if (this.webCamPainter != null) {
            this.webCamPainter.paintPanel(panel, g2);
        }
    }

    @Override
    public void paintImage(WebcamPanel panel, BufferedImage image, Graphics2D g2) {
        if (this.webCamPainter != null) {
            this.webCamPainter.paintImage(panel, image, g2);
        }
        int pw = panel.getWidth(), ph = panel.getHeight();
        int iw = image.getWidth(), ih = image.getHeight();
        int x = 0, y = 0;
        double w = 1, h = 1;
        if (WebcamPanel.DrawMode.FILL == this.webCamPanel.getDrawMode()) {
            w = (double) pw / (double) (iw);
            h = (double) ph / (double) (ih);
        } else if (WebcamPanel.DrawMode.FIT == this.webCamPanel.getDrawMode()) {
            double s = Math.max((double) iw / (double) pw, (double) ih / (double) ph);
            double niw = (double) iw / s;
            double nih = (double) ih / s;
            double dx = ((double) pw - niw) / 2.0;
            double dy = ((double) ph - nih) / 2.0;
            x = (int) dx;
            y = (int) dy;
            w = niw / (double) (iw);
            h = nih / (double) (ih);
        }
        for (FaceInfo faceInfo : this.faceInfoList) {
            Rect rect = faceInfo.getRect();
            Rectangle r = new Rectangle((int) (rect.left * w) + x, (int) (rect.top * h) + y
                    , (int) ((rect.right - rect.left) * w), (int) ((rect.bottom - rect.top) * h));
            if (this.webCamPanel.isMirrored()) {
                r.x = pw - r.width - r.x;
            }
            this.drawRect(g2, r);
        }
    }

    public void drawRect(Graphics2D g2, Rectangle r) {
        g2.setColor(Color.GREEN);
        g2.drawRect(r.x, r.y, r.width, r.height);
    }

    @Override
    public void run() {
        do {
            if (this.webCam == null || !webCam.isOpen() || this.webCamPanel == null) {
                continue;
            }
            BufferedImage image = this.webCamPanel.getImage();
            if (image == null) {
                continue;
            }
            long ms = System.currentTimeMillis();
            FaceEngineService faceEngineService = null;
            try {
                ImageInfo imageInfo = ImageFactory.bufferedImage2GrayImageInfo(image);
                faceEngineService = this.faceEnginePool.borrowObject();
                List<FaceInfo> faceInfoList = faceEngineService.faceDetect(imageInfo);
                for (FaceInfo faceInfo : faceInfoList) {
                    FaceFeature faceFeature = faceEngineService.faceFeature(imageInfo, faceInfo);
                    FaceTrack faceTrack = new FaceTrack(faceInfo.getFaceId(), ImageUtils.readImage(ImageUtils.cutImage(image, faceInfo.getRect())), faceFeature.getFeatureData());
                    this.callback.call(faceTrack);
                }
                this.faceInfoList.clear();
                this.faceInfoList.addAll(faceInfoList);
                DebugUtils.debug("%d\t%dms\t%d", faceEngineService.hashCode(), (System.currentTimeMillis() - ms), faceInfoList.size());
            } catch (Exception e) {
                this.faceInfoList.clear();
            } finally {
                if (faceEngineService != null) {
                    this.faceEnginePool.returnObject(faceEngineService);
                }
            }
        } while (this.running.get());
    }

    interface Callback {
        /**
         * 人脸跟踪
         *
         * @param faceTrack 人脸跟踪
         */
        void call(FaceTrack faceTrack);
    }
}
